package com.woniuxy.portal.utils;

import com.woniuxy.common.consts.RedisConst;
import com.woniuxy.common.consts.UserConst;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.Map;

/**
 * JWT工具类
 */
@Component
@Slf4j
public class JwtUtil {
    //密钥
    @Value("${jwt.secret}")
    private String secret;
    //过期时间
    @Value("${jwt.expiration}")
    private long expiration;

    private final StringRedisTemplate stringRedisTemplate;

    @Autowired
    public JwtUtil(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 生成Token的方法
     *
     * @param claims 自定义的JWT载荷信息，可包含用户ID、用户名等业务数据
     * @return String 生成的JWT Token字符串
     */
    public String createToken(Map<String, Object> claims) {
        //1.创建JWT构建器，并且设置基本的参数
        JwtBuilder builder = Jwts.builder()
                .setSubject("book-mall-system")  //主题
                .setExpiration(new Date(System.currentTimeMillis() + expiration))  //过期时间
                .signWith(SignatureAlgorithm.HS256, secret)  //签名
                .compressWith(CompressionCodecs.GZIP);  //指定压缩格式
        //添加自定义载荷信息到JWT中
        if (claims != null) {
            builder.setClaims(claims);
        }
        //生成并返回JWT Token字符串
        return builder.compact();
    }

    /**
     * 解析Token
     *
     * @param token 待解析的JWT令牌字符串
     * @return Jws<Claims> 解析后的JWS声明对象，包含令牌中的载荷信息
     * @throws JwtException 当令牌格式无效、签名验证失败或过期时抛出异常
     */
    public Jws<Claims> parseToken(String token) {
        //使用JWT解析器验证并解析令牌
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token);
    }

    /**
     * 从授权字符串中提取Token
     * 判断Token前面是否有Bearer字符串，如果有则提取出实际的Token值
     *
     * @param auth 授权字符串，通常以"Bearer "开头的JWT Token
     * @return String 返回提取后的Token字符串，如果不符合格式则返回原字符串
     */
    public String getToken(String auth) {
        //验证授权字符串格式是否正确：不为空、长度大于7、以"Bearer "开头
        if (auth != null && auth.length() > 7 && auth.startsWith("Bearer ")) {
            //从第7个字符开始截取，去掉"Bearer "前缀，返回实际的Token值
            return auth.substring(7);
        }
        return auth;
    }

    /**
     * 获取指定的值
     *
     * @param token 令牌字符串
     * @param key   要获取的值对应的键
     * @return String 指定键对应的值的字符串表示
     */
    public String getValue(String token, String key) {
        //对Token进行处理
        token = getToken(token);
        //解析Token，判断Token是否合法
        Jws<Claims> claimsJws = parseToken(token);
        //获取载荷
        Claims claims = claimsJws.getBody();
        //获取指定的值
        return claims.get(key).toString();
    }

    /**
     * 验证JWT是否有效方法
     *
     * @param token 待验证的JWT令牌
     * @return boolean 验证结果，true表示有效，false表示无效
     */
    public boolean validateToken(String token) {
        //直接调用上面的方法进行解析
        try {
            token = getToken(token);
            parseToken(token);

            //校验token是否在redis中存在且有效
            //得到Redis中键
            String userId = getValue(token, UserConst.USER_ID);
            String key = RedisConst.USER_TOKEN_PREFIX + ":" + userId;
            //获取Redis中Token
            String redisToken = stringRedisTemplate.opsForValue().get(key);
            //比较是否相等
            return token.equals(redisToken);

        } catch (Exception e) {
            log.info("验证Token失败:{}", e.getMessage());
            return false;
        }
    }

    /**
     * 获取客户端真实IP地址的方法
     * 该方法会依次尝试从不同的HTTP头信息中获取IP地址，以应对使用代理或负载均衡的情况
     *
     * @param request HTTP请求对象，用于获取请求头信息和远程地址
     * @return 返回客户端的IP地址字符串，如果无法获取则返回null
     */
    public String getRequestIP(HttpServletRequest request) {
        // 尝试从多个可能的HTTP头中获取客户端IP地址
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        // 如果以上头信息都获取不到，则使用请求的远程地址
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

}
